import com.fazecast.jSerialComm.SerialPort;
import javax.swing.*;
import javax.swing.text.DefaultCaret;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {
    private static boolean running = false; // 运行状态标志
    private static SerialPort serialPort; // 串口对象
    private static ConcurrentLinkedQueue<String> updateQueue = new ConcurrentLinkedQueue<>(); // 用于存储接收到的数据的队列
    private static JTextArea textArea; // 文本区域，用于显示数据
    private static JComboBox<String> portList; // 串口选择下拉框
    private static JComboBox<String> baudRateList; // 波特率选择下拉框
    private static JComboBox<String> baseList; // 进制选择下拉框
    private static JComboBox<String> displayModeList; // 显示模式选择下拉框
    private static JComboBox<String> timeUnitList; // 时间单位选择下拉框
    private static Map<String, List<Integer>> dataMap = new HashMap<>(); // 用于存储数值数据的映射
    private static Map<String, JCheckBox> variableCheckboxMap = new HashMap<>(); // 变量复选框映射
    private static JPanel variablePanel = new JPanel(); // 用于放置变量复选框的面板
    private static DrawingPanel drawingPanel; // 绘图面板
    private static JScrollBar scrollBar; // 滚动条，用于控制显示范围
    private static final int MAX_HISTORY = 100; // 最大历史数据点数
    private static final int VISIBLE_POINTS = 30; // 可见数据点数
    private static final Color[] COLORS = {Color.RED, Color.BLUE, Color.GREEN, Color.ORANGE, Color.MAGENTA, Color.CYAN}; // 颜色数组，用于区分不同变量
    private static int timeUnit = 10; // 时间单位，默认1/10秒
    private static List<String> timeStamps = new ArrayList<>(); // 保存接收数据的时间戳列表
    private static JPanel mainPanel; // 主面板，用于切换不同模式的界面
    private static final Map<String, Color> variableColorMap = new HashMap<>(); // 用于存储每个变量的颜色


    public static void main(String[] args) {
        SwingUtilities.invokeLater(Main::createAndShowGUI); // 使用Swing的事件调度线程创建并显示GUI
    }

    private static void createAndShowGUI() {
        JFrame frame = new JFrame("Serial Port Data Viewer"); // 创建主窗口
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置关闭操作
        frame.setExtendedState(JFrame.MAXIMIZED_BOTH); // 窗口最大化

        JPanel controlPanel1 = new JPanel(); // 创建控制面板
        controlPanel1.setLayout(new FlowLayout()); // 设置布局为流式布局

        Font font = new Font("Arial", Font.PLAIN, 24); // 设置字体

        portList = new JComboBox<>(getAvailablePorts()); // 获取可用的串口列表，并创建下拉框
        portList.setFont(font);
        controlPanel1.add(new JLabel("选择串口:")).setFont(font);
        controlPanel1.add(portList);

        baudRateList = new JComboBox<>(new String[]{"9600", "19200", "38400", "57600", "115200"}); // 创建波特率选择下拉框
        baudRateList.setFont(font);
        controlPanel1.add(new JLabel("选择波特率:")).setFont(font);
        controlPanel1.add(baudRateList);

        displayModeList = new JComboBox<>(new String[]{"原数据", "文本数据", "数值分析"}); // 创建显示模式选择下拉框
        displayModeList.setFont(font);
        displayModeList.addActionListener(e -> updateBaseMenuVisibility()); // 添加显示模式切换的事件监听器
        controlPanel1.add(new JLabel("选择显示模式:")).setFont(font);
        controlPanel1.add(displayModeList);

        baseList = new JComboBox<>(new String[]{"Hex", "Dec", "Bin"}); // 创建进制选择下拉框
        baseList.setFont(font);
        controlPanel1.add(new JLabel("选择进制:")).setFont(font);
        controlPanel1.add(baseList);

        timeUnitList = new JComboBox<>(new String[]{"1/100秒", "1/10秒", "1秒", "10秒"}); // 创建时间单位选择下拉框
        timeUnitList.setFont(font);
        timeUnitList.addActionListener(e -> updateTimeUnit()); // 添加时间单位切换的事件监听器
        controlPanel1.add(new JLabel("选择时间单位:")).setFont(font);
        controlPanel1.add(timeUnitList);

        JButton startButton = new JButton("开始"); // 创建开始按钮
        startButton.setFont(font);
        startButton.addActionListener(e -> toggleReading(startButton)); // 添加开始按钮的事件监听器
        controlPanel1.add(startButton);

        JButton clearButton = new JButton("清空"); // 创建清空按钮
        clearButton.setFont(font);
        clearButton.addActionListener(e -> clearText()); // 添加清空按钮的事件监听器
        controlPanel1.add(clearButton);

        JButton stayButton = new JButton("保持在底部: 开"); // 创建保持在底部按钮
        stayButton.setFont(font);
        stayButton.addActionListener(e -> toggleStayAtBottom(stayButton)); // 添加保持在底部按钮的事件监听器
        controlPanel1.add(stayButton);

        JButton exitButton = new JButton("退出"); // 创建退出按钮
        exitButton.setFont(font);
        exitButton.addActionListener(e -> onExit()); // 添加退出按钮的事件监听器
        controlPanel1.add(exitButton);

        textArea = new JTextArea(); // 创建文本区域
        textArea.setFont(font);
        textArea.setLineWrap(true); // 设置自动换行
        JScrollPane scrollPane = new JScrollPane(textArea); // 将文本区域放入滚动面板
        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); // 设置垂直滚动条总是显示
        DefaultCaret caret = (DefaultCaret) textArea.getCaret(); // 获取文本区域的插入符
        caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); // 设置插入符总是更新

        drawingPanel = new DrawingPanel(); // 创建绘图面板
        drawingPanel.setPreferredSize(new Dimension(800, 600)); // 设置绘图面板的首选大小

        variablePanel.setLayout(new FlowLayout()); // 设置变量面板的布局为流式布局

        scrollBar = new JScrollBar(JScrollBar.HORIZONTAL, 0, VISIBLE_POINTS, 0, MAX_HISTORY);
        scrollBar.setPreferredSize(new Dimension(scrollBar.getPreferredSize().width, 30)); // 设置滚动条高度为30
        scrollBar.addAdjustmentListener(e -> drawingPanel.repaint());

        mainPanel = new JPanel(new CardLayout()); // 创建主面板，并设置为卡片布局，用于切换不同模式的界面

        JPanel defaultPanel = new JPanel(new BorderLayout()); // 创建默认面板，使用边框布局
        defaultPanel.add(scrollPane, BorderLayout.CENTER); // 将滚动面板添加到默认面板的中心

        JPanel analysisPanel = new JPanel(new BorderLayout()); // 创建分析面板，使用边框布局
        JPanel analysisControlPanel = new JPanel(); // 创建分析控制面板
        // analysisControlPanel.setPreferredSize(new Dimension(800, 80));
        analysisControlPanel.setLayout(new FlowLayout()); // 设置为流式布局
        analysisControlPanel.add(variablePanel); // 将变量面板添加到分析控制面板

        JPanel chartPanel = new JPanel(new BorderLayout()); // 创建图表面板，使用边框布局
        chartPanel.add(scrollBar, BorderLayout.SOUTH); // 将滚动条添加到图表面板的南部
        chartPanel.add(drawingPanel, BorderLayout.CENTER); // 将绘图面板添加到图表面板的中心

        analysisPanel.add(analysisControlPanel, BorderLayout.NORTH); // 将分析控制面板添加到分析面板的北部
        analysisPanel.add(chartPanel, BorderLayout.CENTER); // 将图表面板添加到分析面板的中心

        mainPanel.add(defaultPanel, "default"); // 将默认面板添加到主面板，并命名为"default"
        mainPanel.add(analysisPanel, "analysis"); // 将分析面板添加到主面板，并命名为"analysis"

        frame.add(controlPanel1, BorderLayout.NORTH); // 将控制面板添加到主窗口的北部
        frame.add(mainPanel, BorderLayout.CENTER); // 将主面板添加到主窗口的中心

        frame.pack(); // 自动调整窗口大小
        frame.setVisible(true); // 显示窗口

        updateBaseMenuVisibility(); // 更新显示模式
    }

    //使用jSerialComm获取所有可用串口
    private static String[] getAvailablePorts() {
        SerialPort[] ports = SerialPort.getCommPorts(); // 获取所有可用的串口
        String[] portNames = Arrays.stream(ports).map(SerialPort::getSystemPortName).toArray(String[]::new); // 获取串口名称
        return portNames.length > 0 ? portNames : new String[]{"No ports available"}; // 如果没有可用的串口，返回"没有可用的端口"
    }

    //开始按钮事件
    private static void toggleReading(JButton startButton) {
        if (running) {
            running = false; // 停止读取
            startButton.setText("开始"); // 更新按钮文本为"开始"
            if (serialPort != null && serialPort.isOpen()) {
                serialPort.closePort(); // 关闭串口
            }
        } else {
            String selectedPort = (String) portList.getSelectedItem(); // 获取选中的串口
            int baudRate = Integer.parseInt((String) baudRateList.getSelectedItem()); // 获取选中的波特率
            serialPort = SerialPort.getCommPort(selectedPort); // 获取串口对象
            serialPort.setBaudRate(baudRate); // 设置波特率
            if (serialPort.openPort()) {
                running = true; // 开始读取
                startButton.setText("停止"); // 更新按钮文本为"停止"
                new Thread(Main::readSerial).start(); // 创建并启动读取数据的线程
            } else {
                JOptionPane.showMessageDialog(null, "Failed to open port " + selectedPort); // 显示错误消息
            }
        }
    }

    //读取串口
    private static void readSerial() {
        byte[] buffer = new byte[1024]; // 创建缓冲区
        String lastData = null; // 最后一条数据
        long lastTime = System.currentTimeMillis(); // 最后一次读取时间
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS"); // 创建日期格式化对象
        while (running) {
            int bytesRead = serialPort.readBytes(buffer, buffer.length); // 从串口读取数据
            if (bytesRead > 0) {
                String data = new String(buffer, 0, bytesRead, StandardCharsets.UTF_8); // 将数据转换为字符串
                lastData = data; // 更新最后一条数据
            }
            if (System.currentTimeMillis() - lastTime >= timeUnit) {
                if (lastData != null) {
                    String timeStamp = sdf.format(new Date()); // 获取当前时间戳
                    timeStamps.add(timeStamp); // 添加时间戳到列表
                    if (timeStamps.size() > MAX_HISTORY) {
                        timeStamps.remove(0); // 移除最早的时间戳
                    }
                    updateQueue.add(lastData); // 添加数据到队列
                    SwingUtilities.invokeLater(Main::processQueue); // 在事件调度线程中处理队列
                    lastData = null; // 重置最后一条数据
                }
                lastTime = System.currentTimeMillis(); // 更新最后一次读取时间
            }
        }
    }

    //读取串口间隔时间
    private static void updateTimeUnit() {
        String selectedUnit = (String) timeUnitList.getSelectedItem(); // 获取选中的时间单位
        switch (selectedUnit) {
            case "1/100秒":
                timeUnit = 10; // 设置时间单位为1/100秒
                break;
            case "1/10秒":
                timeUnit = 100; // 设置时间单位为1/10秒
                break;
            case "1秒":
                timeUnit = 1000; // 设置时间单位为1秒
                break;
            case "10秒":
                timeUnit = 10000; // 设置时间单位为10秒
                break;
        }
    }

    //切换显示模式
    private static void processQueue() {
        while (!updateQueue.isEmpty()) {
            String message = updateQueue.poll(); // 获取队列中的消息
            String displayMode = (String) displayModeList.getSelectedItem(); // 获取选中的显示模式
            if ("原数据".equals(displayMode)) {
                textArea.append(convertToBase(message, (String) baseList.getSelectedItem()) + "\n"); // 显示原数据
            } else if ("文本数据".equals(displayMode)) {
                textArea.append(message + "\n"); // 显示文本数据
            } else if ("数值分析".equals(displayMode)) {
                processNumericalData(message); // 处理数值数据
            }
        }
    }

    //进制转换
    private static String convertToBase(String data, String base) {
        StringBuilder sb = new StringBuilder(); // 创建字符串构建器
        for (char c : data.toCharArray()) {
            switch (base) {
                case "Hex":
                    sb.append(String.format("%02X ", (int) c)); // 将字符转换为十六进制
                    break;
                case "Dec":
                    sb.append(String.format("%03d ", (int) c)); // 将字符转换为十进制
                    break;
                case "Bin":
                    sb.append(String.format("%08d ", Integer.parseInt(Integer.toBinaryString((int) c)))); // 将字符转换为二进制
                    break;
            }
        }
        return sb.toString(); // 返回转换后的字符串
    }

    //数值分析界面变量统计
    private static void processNumericalData(String data) {
        Pattern pattern = Pattern.compile("(\\w+)[:=\\-, ](\\d+)");
        Matcher matcher = pattern.matcher(data);
        while (matcher.find()) {
            String variable = matcher.group(1);
            int value = Integer.parseInt(matcher.group(2));

            dataMap.putIfAbsent(variable, new ArrayList<>());
            List<Integer> values = dataMap.get(variable);
            values.add(value);
            if (values.size() > MAX_HISTORY) {
                values.remove(0);
            }
            if (!variableCheckboxMap.containsKey(variable)) {
                JCheckBox checkBox = new JCheckBox(variable);
                checkBox.setFont(new Font("Arial", Font.PLAIN, 28));
                checkBox.setSelected(true);
                checkBox.setMinimumSize(new Dimension(100, 60));
                checkBox.setPreferredSize(new Dimension(100, 60));
                checkBox.setHorizontalAlignment(SwingConstants.CENTER);
                checkBox.setOpaque(true);

                Color variableColor = COLORS[variableCheckboxMap.size() % COLORS.length];
                variableColorMap.put(variable, variableColor);
                checkBox.setBackground(variableColor);
                checkBox.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY));
                checkBox.setFocusPainted(false);
                checkBox.setContentAreaFilled(true);

                checkBox.addActionListener(e -> {
                    if (checkBox.isSelected()) {
                        checkBox.setBackground(variableColorMap.get(variable));
                    } else {
                        checkBox.setBackground(Color.WHITE);
                    }
                    drawingPanel.repaint();
                });

                variableCheckboxMap.put(variable, checkBox);
                variablePanel.add(checkBox);
                variablePanel.revalidate();
                variablePanel.repaint();
            }
        }
        drawingPanel.repaint();
    }

    //清楚缓存
    private static void clearText() {
        textArea.setText(""); // 清空文本区域
        dataMap.clear(); // 清空数据映射
        variableCheckboxMap.clear(); // 清空复选框映射
        variablePanel.removeAll(); // 移除所有变量面板中的组件
        drawingPanel.repaint(); // 重绘绘图面板
    }

    //是否保持在底部
    private static void toggleStayAtBottom(JButton stayButton) {
        DefaultCaret caret = (DefaultCaret) textArea.getCaret(); // 获取文本区域的插入符
        if (caret.getUpdatePolicy() == DefaultCaret.ALWAYS_UPDATE) {
            caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE); // 设置插入符更新策略为从不更新
            stayButton.setText("保持在底部: 关"); // 更新按钮文本
        } else {
            caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); // 设置插入符更新策略为总是更新
            stayButton.setText("保持在底部: 开"); // 更新按钮文本
        }
    }

    //面板切换
    private static void updateBaseMenuVisibility() {
        String displayMode = (String) displayModeList.getSelectedItem(); // 获取选中的显示模式
        CardLayout cl = (CardLayout)(mainPanel.getLayout()); // 获取主面板的卡片布局
        if ("数值分析".equals(displayMode)) {
            cl.show(mainPanel, "analysis"); // 显示分析面板
        } else {
            cl.show(mainPanel, "default"); // 显示默认面板
        }
        baseList.setVisible("原数据".equals(displayMode)); // 设置进制选择下拉框的可见性
    }

    //退出
    private static void onExit() {
        running = false; // 设置运行状态为false
        if (serialPort != null && serialPort.isOpen()) {
            serialPort.closePort(); // 关闭串口
        }
        System.exit(0); // 退出程序
    }

    //绘图
    static class DrawingPanel extends JPanel {
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g;
            int width = getWidth();
            int height = getHeight();
            g2d.setColor(Color.WHITE);
            g2d.fillRect(0, 0, width, height);
            g2d.setColor(Color.BLACK);

            int margin = 50;
            int maxDataPoints = VISIBLE_POINTS;

            List<Integer> allSelectedValues = new ArrayList<>();
            for (Map.Entry<String, List<Integer>> entry : dataMap.entrySet()) {
                if (variableCheckboxMap.get(entry.getKey()).isSelected()) {
                    allSelectedValues.addAll(entry.getValue());
                }
            }
            int maxValue = allSelectedValues.stream().max(Integer::compare).orElse(100);
            int minValue = allSelectedValues.stream().min(Integer::compare).orElse(0);
            int range = maxValue - minValue;
            if (range == 0) {
                range = 1;
            }

            g2d.drawLine(margin, height - margin, width - margin, height - margin);
            g2d.drawLine(margin, height - margin, margin, margin);

            Font axisFont = new Font("Arial", Font.PLAIN, 22);
            g2d.setFont(axisFont);

            g2d.setColor(new Color(144, 238, 144));
            g2d.setStroke(new BasicStroke(1.5f));
            for (int i = 0; i <= maxDataPoints; i++) {
                int x = margin + i * (width - 2 * margin) / maxDataPoints;
                g2d.drawLine(x, height - margin, x, margin);
            }
            for (int i = 0; i <= 10; i++) {
                int y = height - margin - i * (height - 2 * margin) / 10;
                g2d.drawLine(margin, y, width - margin, y);
            }

            g2d.setColor(Color.BLACK);
            for (int i = 0; i < maxDataPoints; i++) {
                if (i % 5 == 0) {
                    int x = margin + i * (width - 2 * margin) / maxDataPoints;
                    g2d.drawLine(x, height - margin, x, height - margin + 5);
                    int dataIndex = scrollBar.getValue() + i;
                    if (dataIndex < timeStamps.size()) {
                        g2d.drawString(timeStamps.get(dataIndex), x - 20, height - margin + 25);
                    }
                }
            }

            for (int i = 0; i <= 10; i++) {
                int y = height - margin - i * (height - 2 * margin) / 10;
                g2d.drawLine(margin - 5, y, margin, y);
                g2d.drawString(String.valueOf(minValue + i * range / 10), margin - 45, y + 5);
            }

            for (Map.Entry<String, List<Integer>> entry : dataMap.entrySet()) {
                String variable = entry.getKey();
                List<Integer> values = entry.getValue();

                if (variableCheckboxMap.get(variable).isSelected()) {
                    int pointCount = Math.min(values.size(), maxDataPoints);
                    int xStep = (width - 2 * margin) / maxDataPoints;

                    g2d.setColor(variableColorMap.get(variable));

                    for (int i = 0; i < pointCount - 1; i++) {
                        int index = scrollBar.getValue() + i;
                        if (index >= values.size() - 1) {
                            break;
                        }
                        int x1 = margin + i * xStep;
                        int y1 = height - margin - ((values.get(index) - minValue) * (height - 2 * margin)) / range;
                        int x2 = margin + (i + 1) * xStep;
                        int y2 = height - margin - ((values.get(index + 1) - minValue) * (height - 2 * margin)) / range;
                        g2d.drawLine(x1, y1, x2, y2);
                    }
                }
            }
        }
    }
}
